Conversation
명예의 전당에서 프사 안보임. 아카이브, 스터디 기록이 안뜸. 직무 유저가 설정한 그대로 표기되도록 개선.
fix: 명예의전당, 아카이브, 스터디 기록에서 2가지 버그 픽스
…-fix-3 fix: 비회원일때 생기는 각종 버그 수정
fix: 스크롤 문제 해결
댓글 개수 제대로 안 나오는 부분 수정
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 Walkthrough🎯 전체 요약Walkthrough이 변경사항은 인증 기반의 접근 제어 추가, ProfileAvatar에서 UserAvatar로의 컴포넌트 마이그레이션, 스레드 및 댓글 API의 페이지네이션 지원, 그리고 파트너 데이터의 nullable 타입 변경을 포함합니다. CodeRabbit 자동 리뷰 설정도 추가되었으며, 레이아웃 및 스타일 조정이 함께 이루어졌습니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~35 minutes 다양한 테마(인증 게이팅, 컴포넌트 마이그레이션, API 페이지네이션, 타입 시스템 업데이트)에 걸친 광범위한 변경이지만, 각 코호트는 일관성 있는 패턴을 따릅니다. 페이지네이션 API 변경은 여러 파일에 영향을 미치지만 명확한 구조를 가지고 있으며, 인증 게이팅은 반복적인 패턴입니다. 타입 변경 및 nullable 업데이트도 비교적 간단합니다. Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/app/(service)/layout.tsx (1)
35-37:⚠️ Potential issue | 🟠 MajorClarity 초기화가 Server Component에서 실행되지 않습니다.
이 파일은 Server Component입니다 (
export const metadata와 비동기 렌더링 사용). 서버에서는typeof window !== 'undefined'조건이 항상false이므로Clarity.init()이 절대 호출되지 않습니다.Clarity를 올바르게 초기화하려면 별도의 Client Component를 만들어
useEffect내에서 초기화해야 합니다.🐛 Client Component로 분리하는 수정 제안
새 파일
src/components/analytics/clarity-init.tsx생성:'use client'; import Clarity from '@microsoft/clarity'; import { useEffect } from 'react'; export default function ClarityInit({ projectId }: { projectId: string }) { useEffect(() => { Clarity.init(projectId); }, [projectId]); return null; }그리고 레이아웃에서 사용:
- if (typeof window !== 'undefined' && CLARITY_PROJECT_ID) { - Clarity.init(CLARITY_PROJECT_ID); - } + // Clarity initialization moved to ClarityInit component return ( <html lang="ko"> <head>{GTM_ID && <GoogleTagManager gtmId={GTM_ID} />}</head> <body className={clsx(pretendard.className, 'min-h-screen w-screen')}> <MainProvider> + {CLARITY_PROJECT_ID && <ClarityInit projectId={CLARITY_PROJECT_ID} />} <PageViewTracker />src/components/voting/voting-detail-view.tsx (1)
254-262:⚠️ Potential issue | 🟡 Minor프로덕션 코드에 디버깅용 console.log가 남아있습니다.
디버깅용 로그가 프로덕션 환경에서도 출력됩니다. 개발 환경에서만 출력되도록 조건을 추가하거나 제거해주세요.
- // 디버깅용 로그 추가 - console.log('Voting Data:', { - myVote: voting.myVote, - hasVoted, - endsAt: voting.endsAt, - rawIsActive: voting.isActive, - isActiveByEndsAt, - isActive, - });src/features/study/one-to-one/schedule/ui/today-study-card.tsx (1)
63-69:⚠️ Potential issue | 🔴 Critical
realNameprop이PartnerInfo컴포넌트에 전달되지 않습니다.
partner객체에realName필드를 추가했지만 (라인 34-36),PartnerInfo컴포넌트 호출 시 해당 prop을 전달하지 않았습니다. 이로 인해PartnerInfo내부의realName이 항상undefined가 되어realName ?? namefallback이 항상name을 사용하게 됩니다.🐛 누락된 prop 전달 수정 제안
<PartnerInfo id={partner.id} name={partner.name} + realName={partner.realName} image={partner.image} phoneNumber={partner.tel} isInterviewee={partner.id === todayStudyData.intervieweeId} />
🤖 Fix all issues with AI agents
In `@next.config.ts`:
- Line 2: Remove the internal import "RemotePattern" from
'next/dist/shared/lib/image-config' and instead let Next.js infer the types via
the NextConfig export (use the NextConfig object/type rather than an internal
RemotePattern), and modify the images.remotePatterns array in the exported
NextConfig so that both localhost patterns (the two patterns matching
localhost:1337) are only included when isProd is false (i.e., guard them with
the isProd check or conditionally push them into images.remotePatterns when not
in production) so local-image access is disabled in production.
In `@src/app/`(service)/home/home-content.tsx:
- Around line 16-25: The server-side tab rendering logic in renderTabContent
uses getServerCookie('memberId') and isNumeric(memberIdStr) which can disagree
with the client-side useAuth().isAuthenticated check; update the server-side
check to mirror the client-side authentication (e.g., verify accessToken/role or
call the same server auth utility used by useAuth) before rendering
StudyHistoryTab, or alternatively ensure StudyHistoryTab performs its own
authorization check and falls back to StudyTab if unauthorized; change the logic
around getServerCookie/memberIdStr in renderTabContent so it uses the unified
auth verification (or delegates to StudyHistoryTab) to prevent guest users with
a memberId cookie from seeing history content.
In `@src/components/study-history/study-history-row.tsx`:
- Around line 38-55: Change the trigger element for UserProfileModal from a
non-semantic div to a semantic button to improve accessibility: replace the div
used as the trigger with a <button> element (retain the existing className,
onClick={(e) => e.stopPropagation()} handler, and children including UserAvatar
and partner.name) so UserProfileModal's trigger prop continues to receive the
same element; ensure the button has type="button" and any necessary aria-labels
if the visual text isn't sufficient. This affects the UserProfileModal usage in
the component where partner is rendered (the trigger prop passed into
UserProfileModal).
In `@src/features/study/group/channel/model/use-channel-query.ts`:
- Around line 46-48: useThreadsQuery currently forwards whatever `page` it
receives into `getThreads` (after subtracting 1) which allows invalid
zero/negative pages to produce invalid requests; validate/sanitize `page` at the
hook boundary by computing a safePage = Math.max(1, page || 1), use that
safePage for the queryKey (e.g., ['get-threads', groupStudyId, safePage, size])
and pass getThreads({ groupStudyId, page: safePage - 1, size }) so the hook
never sends page < 0 to getThreads; update any local variable names (safePage)
and ensure enabled remains !!groupStudyId.
In `@src/features/study/group/channel/ui/comment-section.tsx`:
- Line 50: Remove the stray console.log(data.content, 'data') from
CommentSection (the debug line printing data.content) to avoid noisy/PII logs;
either delete the line entirely or replace it with a proper application logger
call (e.g., logger.debug) that does not print raw user content, keeping any
necessary non-sensitive context only.
- Around line 68-70: The component currently returns null when either isLoading
or !allCommentsLoaded is true, hiding the entire comment section until every
subcomment finishes; change the logic to only gate on the thread-level loading
flag so subthreads can render while their replies load: remove the
!allCommentsLoaded check from the early return (keep only isLoading) in the
section that references isLoading and allCommentsLoaded (the block using
useThreadsQuery and rendering SubComments), relying on each SubComments
component to handle its own loading state.
In `@src/features/study/group/channel/ui/sub-comments.tsx`:
- Line 32: Remove the stray debug console.log in
src/features/study/group/channel/ui/sub-comments.tsx — specifically the
console.log(data, 'subcomment') call; either delete it or replace it with a
proper, environment-guarded logger (e.g., only log in development) or use
existing app logging utilities so that sensitive data isn't printed to the
console.
🧹 Nitpick comments (6)
src/components/card/voting-card.tsx (1)
93-94: 주석과 코드 동작이 일치하지 않습니다.주석은 일반적인 "투표 결과 미리보기"로 변경되었지만, 실제 코드는 여전히
hasVoted조건으로 투표한 경우에만 표시됩니다. 주석을 코드 동작에 맞게 수정하는 것이 좋습니다.- {/* 간단한 투표 결과 미리보기 */} + {/* 투표한 경우에만 간단한 투표 결과 미리보기 */}src/types/hall-of-fame.ts (1)
15-16: import 문을 파일 상단으로 이동하세요.import 문이 파일 중간에 위치해 있습니다. TypeScript에서는 동작하지만, 일반적인 컨벤션은 모든 import를 파일 상단에 배치하는 것입니다.
♻️ 제안된 수정
// 명예의 전당 API 타입 정의 + +import type { ProfileImage } from '@/entities/user/api/types'; export interface ApiResponse<T> { statusCode: number; timestamp: string; content: T; message: string; } export interface Job { job?: string; // Enum 값 (예: "IT_PRACTITIONER_GAME_DEV") description?: string; // 설명 (예: "게임 개발자") } -import type { ProfileImage } from '@/entities/user/api/types'; - export interface Ranker {src/types/study-history.ts (1)
1-1: 타입 정의 파일에서'use client'제거를 고려하세요.이 파일은 TypeScript 타입 정의만 포함하고 있어 런타임에 존재하지 않습니다.
'use client'지시어는 클라이언트 컴포넌트에만 필요하며, 타입 파일에는 불필요합니다.♻️ 제안된 수정
-'use client'; - // 백엔드 API 응답 구조에 맞춘 타입 정의src/features/study/one-to-one/archive/model/use-archive-actions.ts (1)
5-6: import와 interface 사이에 빈 줄을 추가하세요.코드 가독성을 위해 import 문과 다른 선언 사이에 빈 줄을 추가하는 것이 좋습니다.
♻️ 제안된 수정
import { useAuth } from '@/hooks/common/use-auth'; + interface ArchiveViewTarget {src/features/study/one-to-one/archive/ui/archive-tab-client.tsx (1)
82-96: 인증 가드 중복을 고려해 주세요.
handleLike와handleLibraryBookmark에서isAuthenticated체크가 있지만,useArchiveActions내부의toggleLike와toggleBookmark에서 이미 동일한 가드를 수행합니다. 중복 가드는 방어적 프로그래밍으로 볼 수도 있지만, 불필요한 코드일 수도 있습니다.♻️ 중복 제거 제안 (선택적)
const handleLike = (e: React.MouseEvent, id: number) => { e.stopPropagation(); - if (!isAuthenticated) return; toggleLike(id); }; const handleView = (item: ArchiveItem) => { openAndRecordView(item); }; const handleLibraryBookmark = (e: React.MouseEvent, id: number) => { e.stopPropagation(); - if (!isAuthenticated) return; toggleBookmark(id); };src/features/study/group/channel/ui/comment-section.tsx (1)
33-49: 답글을 전부 병렬 호출하면 요청 수가 급증할 수 있습니다.페이지당 스레드 수가 많아질수록 댓글 API가 N+1 형태로 호출되어 초기 로딩과 서버 부하가 커질 수 있습니다. replyCount를 서버에서 내려주거나, 답글을 펼칠 때만 조회하는 방식으로 전환을 고려해주세요.
| @@ -1,4 +1,7 @@ | |||
| import type { NextConfig } from 'next'; | |||
| import type { RemotePattern } from 'next/dist/shared/lib/image-config'; | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the current state of next.config.ts
cd /tmp && find . -name "next.config.ts" -o -name "next.config.js" | head -5Repository: code-zero-to-one/study-platform-client
Length of output: 64
🏁 Script executed:
# List repository files to understand structure
git ls-files | grep -E "(next\.config|tsconfig)" | head -10Repository: code-zero-to-one/study-platform-client
Length of output: 110
🏁 Script executed:
# Look for next.config file
fd -t f "next\.config" . --max-results 5Repository: code-zero-to-one/study-platform-client
Length of output: 98
🏁 Script executed:
cat -n next.config.tsRepository: code-zero-to-one/study-platform-client
Length of output: 3202
🌐 Web query:
Next.js RemotePattern type import next/dist/shared/lib/image-config official documentation
💡 Result:
There is no official Next.js documentation recommending (or supporting) importing RemotePattern from the internal path next/dist/shared/lib/image-config. That path is internal/unstable (can change between releases) and is not part of the public API surface.
Officially documented approach: type your config via NextConfig, and just write images.remotePatterns per the docs. [1]
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{ protocol: "https", hostname: "example.com", port: "", pathname: "/**", search: "" },
],
},
};
export default nextConfig;If you want a reusable type without importing internal modules, derive it from NextConfig:
import type { NextConfig } from "next";
export type RemotePattern =
NonNullable<NonNullable<NextConfig["images"]>["remotePatterns"]>[number];Official docs references for remotePatterns: Next.js Image component docs (remotePatterns section). [1]
(Also see the images config option docs page.) [2]
Sources:
[1] https://nextjs.org/docs/app/api-reference/components/image
[2] https://nextjs.org/docs/pages/api-reference/config/next-config-js/images
🌐 Web query:
Next.js images.remotePatterns type definition Next.js 15
💡 Result:
In Next.js 15, images.remotePatterns is typed as an array of remote pattern objects, and (starting in Next.js 15.3.0) it also accepts URL objects. (nextjs.org)
A practical TypeScript shape is:
type RemotePattern = {
protocol?: 'http' | 'https'
hostname: string
port?: string
pathname?: string
search?: string
}
type ImagesConfig = {
remotePatterns?: Array<RemotePattern | URL> // URL supported in >= 15.3.0
}Notes:
- With the object form,
searchis optional; omitting it allows any querystring. (nextjs.org) - With the
new URL(...)form, if you don’t include a search in the URL, thesearchbecomes''(i.e., querystrings are not allowed). (nextjs.org)
Citations:
- 1: https://nextjs.org/docs/pages/api-reference/components/image?utm_source=openai
- 2: https://nextjs.org/docs/pages/api-reference/components/image?utm_source=openai
- 3: https://nextjs.org/docs/messages/next-image-unconfigured-host?utm_source=openai
내부 타입 임포트 제거 및 로컬 이미지 패턴을 비프로덕션으로 한정하세요.
현재 next/dist/shared/lib/image-config는 Next.js 공식 공개 API가 아닌 내부 경로로, 버전 업 시 변경될 수 있습니다. 또한 localhost:1337 패턴(47-52줄)이 무조건 포함되어 있어 프로덕션에서도 로컬 서비스 접근이 가능합니다. 사용자 입력 이미지 URL이 있을 경우 SSRF 위험이 생깁니다.
공식 문서에서 권장하는 방식으로 NextConfig에서 타입을 유도하고, 두 localhost 패턴을 모두 isProd 조건으로 보호하세요.
🔧 제안 변경
-import type { RemotePattern } from 'next/dist/shared/lib/image-config';
+type RemotePattern = NonNullable<
+ NonNullable<NextConfig['images']>['remotePatterns']
+>[number];
const isProd = process.env.NODE_ENV === 'production';
+const localRemotePatterns: RemotePattern[] = isProd
+ ? []
+ : [
+ {
+ protocol: 'http',
+ hostname: 'localhost',
+ port: '8080',
+ pathname: '/**',
+ },
+ {
+ protocol: 'http',
+ hostname: 'localhost',
+ port: '1337',
+ pathname: '/uploads/**',
+ },
+ ];
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'img1.kakaocdn.net',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'lh3.googleusercontent.com',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'test-api.zeroone.it.kr',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'api.zeroone.it.kr',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'www.zeroone.it.kr',
pathname: '/**',
},
- ...(isProd
- ? ([] as RemotePattern[])
- : ([
- {
- protocol: 'http',
- hostname: 'localhost',
- port: '8080',
- pathname: '/**',
- },
- ] as RemotePattern[])),
- {
- protocol: 'http',
- hostname: 'localhost',
- port: '1337',
- pathname: '/uploads/**',
- },
+ ...localRemotePatterns,
{
protocol: 'https',
hostname: 'test-blog.zeroone.it.kr',
pathname: '/uploads/**',
},
],
},Also applies to: 37-52
🤖 Prompt for AI Agents
In `@next.config.ts` at line 2, Remove the internal import "RemotePattern" from
'next/dist/shared/lib/image-config' and instead let Next.js infer the types via
the NextConfig export (use the NextConfig object/type rather than an internal
RemotePattern), and modify the images.remotePatterns array in the exported
NextConfig so that both localhost patterns (the two patterns matching
localhost:1337) are only included when isProd is false (i.e., guard them with
the isProd check or conditionally push them into images.remotePatterns when not
in production) so local-image access is disabled in production.
| const isHistoryTab = activeTab === 'history'; | ||
| const memberIdStr = isHistoryTab ? await getServerCookie('memberId') : null; | ||
| const isLoggedIn = !!memberIdStr && isNumeric(memberIdStr); | ||
|
|
||
| const renderTabContent = () => { | ||
| switch (activeTab) { | ||
| case 'study': | ||
| return <StudyTab />; | ||
| case 'history': | ||
| return <StudyHistoryTab />; | ||
| return isLoggedIn ? <StudyHistoryTab /> : <StudyTab />; |
There was a problem hiding this comment.
클라이언트와 서버 간 인증 확인 로직 불일치 가능성이 있습니다.
tab-navigation.tsx에서는 useAuth().isAuthenticated && getCookie('memberId')로 탭 가시성을 결정하지만, 여기서는 getServerCookie('memberId') && isNumeric(memberIdStr)만 확인합니다.
useAuth는 accessToken 유효성과 ROLE_GUEST 여부까지 검사하므로, 서버에서 memberId 쿠키만 확인하면 다음과 같은 엣지 케이스가 발생할 수 있습니다:
- 게스트 사용자가
memberId쿠키를 가진 경우: 탭은 숨겨지지만 직접 URL 접근 시StudyHistoryTab이 렌더링됨
서버에서도 동일한 인증 수준의 검증을 적용하거나, StudyHistoryTab 내부에서 추가 권한 검증을 수행하는 것을 권장합니다.
🤖 Prompt for AI Agents
In `@src/app/`(service)/home/home-content.tsx around lines 16 - 25, The
server-side tab rendering logic in renderTabContent uses
getServerCookie('memberId') and isNumeric(memberIdStr) which can disagree with
the client-side useAuth().isAuthenticated check; update the server-side check to
mirror the client-side authentication (e.g., verify accessToken/role or call the
same server auth utility used by useAuth) before rendering StudyHistoryTab, or
alternatively ensure StudyHistoryTab performs its own authorization check and
falls back to StudyTab if unauthorized; change the logic around
getServerCookie/memberIdStr in renderTabContent so it uses the unified auth
verification (or delegates to StudyHistoryTab) to prevent guest users with a
memberId cookie from seeing history content.
| {partner ? ( | ||
| <UserProfileModal | ||
| memberId={partner.id} | ||
| trigger={ | ||
| <div | ||
| className="flex cursor-pointer items-center gap-100 transition-opacity hover:opacity-80" | ||
| onClick={(e) => e.stopPropagation()} // 행 클릭 이벤트(링크 이동) 방지 | ||
| > | ||
| <UserAvatar | ||
| image={partner.profileImage || undefined} | ||
| alt={partner.name} | ||
| size={32} | ||
| className="h-8 w-8" | ||
| /> | ||
| <span className="text-text-default truncate font-medium"> | ||
| {partner.name} | ||
| </span> | ||
| </div> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/components/study-history/study-history-row.tsxRepository: code-zero-to-one/study-platform-client
Length of output: 5796
🏁 Script executed:
find . -name "*user-profile-modal*" -type fRepository: code-zero-to-one/study-platform-client
Length of output: 127
🏁 Script executed:
cat -n src/entities/user/ui/user-profile-modal.tsxRepository: code-zero-to-one/study-platform-client
Length of output: 13462
🏁 Script executed:
find . -name "*modal*" -path "*/ui/*" -type f | grep -E "\.(tsx|ts)$" | head -20Repository: code-zero-to-one/study-platform-client
Length of output: 1129
🏁 Script executed:
find . -path "*/ui/modal*" -type f | grep -v node_modulesRepository: code-zero-to-one/study-platform-client
Length of output: 152
🏁 Script executed:
cat -n src/components/ui/modal/index.tsxRepository: code-zero-to-one/study-platform-client
Length of output: 5202
트리거 요소를 의미론적으로 더 나은 button 요소로 변경해주세요.
현재 div는 Radix UI의 asChild 처리로 기본 키보드 접근성을 지원하지만, 시맨틱 HTML 사용이 스크린 리더 및 접근성 표준에 더 부합합니다.
제안 수정안
- <div
- className="flex cursor-pointer items-center gap-100 transition-opacity hover:opacity-80"
- onClick={(e) => e.stopPropagation()} // 행 클릭 이벤트(링크 이동) 방지
- >
+ <button
+ type="button"
+ className="flex cursor-pointer items-center gap-100 transition-opacity hover:opacity-80"
+ onClick={(e) => e.stopPropagation()} // 행 클릭 이벤트(링크 이동) 방지
+ >
<UserAvatar
image={partner.profileImage || undefined}
alt={partner.name}
size={32}
className="h-8 w-8"
/>
<span className="text-text-default truncate font-medium">
{partner.name}
</span>
- </div>
+ </button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {partner ? ( | |
| <UserProfileModal | |
| memberId={partner.id} | |
| trigger={ | |
| <div | |
| className="flex cursor-pointer items-center gap-100 transition-opacity hover:opacity-80" | |
| onClick={(e) => e.stopPropagation()} // 행 클릭 이벤트(링크 이동) 방지 | |
| > | |
| <UserAvatar | |
| image={partner.profileImage || undefined} | |
| alt={partner.name} | |
| size={32} | |
| className="h-8 w-8" | |
| /> | |
| <span className="text-text-default truncate font-medium"> | |
| {partner.name} | |
| </span> | |
| </div> | |
| {partner ? ( | |
| <UserProfileModal | |
| memberId={partner.id} | |
| trigger={ | |
| <button | |
| type="button" | |
| className="flex cursor-pointer items-center gap-100 transition-opacity hover:opacity-80" | |
| onClick={(e) => e.stopPropagation()} // 행 클릭 이벤트(링크 이동) 방지 | |
| > | |
| <UserAvatar | |
| image={partner.profileImage || undefined} | |
| alt={partner.name} | |
| size={32} | |
| className="h-8 w-8" | |
| /> | |
| <span className="text-text-default truncate font-medium"> | |
| {partner.name} | |
| </span> | |
| </button> |
🤖 Prompt for AI Agents
In `@src/components/study-history/study-history-row.tsx` around lines 38 - 55,
Change the trigger element for UserProfileModal from a non-semantic div to a
semantic button to improve accessibility: replace the div used as the trigger
with a <button> element (retain the existing className, onClick={(e) =>
e.stopPropagation()} handler, and children including UserAvatar and
partner.name) so UserProfileModal's trigger prop continues to receive the same
element; ensure the button has type="button" and any necessary aria-labels if
the visual text isn't sufficient. This affects the UserProfileModal usage in the
component where partner is rendered (the trigger prop passed into
UserProfileModal).
| queryKey: ['get-threads', groupStudyId, page, size], | ||
| queryFn: () => getThreads({ groupStudyId, page: page - 1, size }), | ||
| enabled: !!groupStudyId, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/features/study/group/channel/model/use-channel-query.ts | head -70Repository: code-zero-to-one/study-platform-client
Length of output: 2617
🏁 Script executed:
# Check the full function signature and any validation
rg -A 20 "useThreadsQuery|UseThreadsQueryParams" src/features/study/group/channel/model/use-channel-query.tsRepository: code-zero-to-one/study-platform-client
Length of output: 859
🏁 Script executed:
# Look for type definition of UseThreadsQueryParams
rg "UseThreadsQueryParams|interface.*UseThreadsQueryParams|type.*UseThreadsQueryParams" -A 5 src/Repository: code-zero-to-one/study-platform-client
Length of output: 1146
🏁 Script executed:
# Search for usages of useThreadsQuery to understand how page is passed
rg "useThreadsQuery" --type ts --type tsx -B 2 -A 2Repository: code-zero-to-one/study-platform-client
Length of output: 109
🏁 Script executed:
# Search for usages of useThreadsQuery to see how page parameter is passed
rg "useThreadsQuery" -B 2 -A 2 --type tsRepository: code-zero-to-one/study-platform-client
Length of output: 1278
🏁 Script executed:
# Also search in tsx files differently
fd -e tsx -e ts | xargs grep -l "useThreadsQuery" | head -10Repository: code-zero-to-one/study-platform-client
Length of output: 197
🏁 Script executed:
# Check all usages more broadly
rg "useThreadsQuery\(" -B 3 -A 3Repository: code-zero-to-one/study-platform-client
Length of output: 736
🏁 Script executed:
# Examine comment-section.tsx to see how page is initialized and managed
cat -n src/features/study/group/channel/ui/comment-section.tsx | head -150Repository: code-zero-to-one/study-platform-client
Length of output: 5780
🏁 Script executed:
# Search for page state and its initialization in comment-section.tsx
rg "page|useState" src/features/study/group/channel/ui/comment-section.tsx -B 1 -A 1 | head -50Repository: code-zero-to-one/study-platform-client
Length of output: 682
🏁 Script executed:
# Check the Pagination component to see if it validates page values
fd -name "pagination*" -type fRepository: code-zero-to-one/study-platform-client
Length of output: 312
🏁 Script executed:
# Find and examine the Pagination component
rg "export.*Pagination|function.*Pagination|const.*Pagination" -A 15 src/Repository: code-zero-to-one/study-platform-client
Length of output: 5364
🏁 Script executed:
# Check the setPage usage in comment-section.tsx - look for the full pagination section
sed -n '150,200p' src/features/study/group/channel/ui/comment-section.tsxRepository: code-zero-to-one/study-platform-client
Length of output: 622
🏁 Script executed:
# Examine the Pagination component implementation
cat -n src/components/ui/pagination/index.tsxRepository: code-zero-to-one/study-platform-client
Length of output: 5727
🏁 Script executed:
# Also check if there's a helper function for page handling
rg "getVisibleMiddlePages|onChangePage" src/components/ui/pagination/ -A 5Repository: code-zero-to-one/study-platform-client
Length of output: 3348
useThreadsQuery에 페이지 값 최소값 검증 추가 필요
현재 Pagination 컴포넌트는 page < 1인 값을 전달하지 않도록 제어하고 있으나, useThreadsQuery 훅 자체에는 입력 값 검증이 없습니다. 만약 훅을 직접 호출할 때 page: 0 또는 음수 값이 전달되면 getThreads에 유효하지 않은 페이지 요청(page: -1 등)이 전달됩니다. 입력값 검증을 추가하여 안전성을 확보하세요.
제안 수정안
export const useThreadsQuery = ({
groupStudyId,
page = 1,
size = 10,
}: UseThreadsQueryParams) => {
+ const safePage = Math.max(1, page);
return useQuery({
- queryKey: ['get-threads', groupStudyId, page, size],
- queryFn: () => getThreads({ groupStudyId, page: page - 1, size }),
+ queryKey: ['get-threads', groupStudyId, safePage, size],
+ queryFn: () => getThreads({ groupStudyId, page: safePage - 1, size }),
enabled: !!groupStudyId,
placeholderData: keepPreviousData,
});
};🤖 Prompt for AI Agents
In `@src/features/study/group/channel/model/use-channel-query.ts` around lines 46
- 48, useThreadsQuery currently forwards whatever `page` it receives into
`getThreads` (after subtracting 1) which allows invalid zero/negative pages to
produce invalid requests; validate/sanitize `page` at the hook boundary by
computing a safePage = Math.max(1, page || 1), use that safePage for the
queryKey (e.g., ['get-threads', groupStudyId, safePage, size]) and pass
getThreads({ groupStudyId, page: safePage - 1, size }) so the hook never sends
page < 0 to getThreads; update any local variable names (safePage) and ensure
enabled remains !!groupStudyId.
| (sum, q) => sum + (q.data?.totalElements ?? 0), | ||
| 0, | ||
| ); | ||
| console.log(data.content, 'data'); |
There was a problem hiding this comment.
디버그 로그는 제거하는 편이 좋습니다.
운영 콘솔 노이즈/개인정보 노출 위험을 줄이기 위해 정리해주세요.
🧹 제안 수정안
- console.log(data.content, 'data');📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| console.log(data.content, 'data'); |
🤖 Prompt for AI Agents
In `@src/features/study/group/channel/ui/comment-section.tsx` at line 50, Remove
the stray console.log(data.content, 'data') from CommentSection (the debug line
printing data.content) to avoid noisy/PII logs; either delete the line entirely
or replace it with a proper application logger call (e.g., logger.debug) that
does not print raw user content, keeping any necessary non-sensitive context
only.
| // threads 로딩 중이거나 답글 로딩 중이면 대기 | ||
| if (isLoading || !allCommentsLoaded) return null; | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd src/features/study/group/channel/ui && head -100 comment-section.tsxRepository: code-zero-to-one/study-platform-client
Length of output: 3023
🏁 Script executed:
cd src/features/study/group/channel/ui && sed -n '70,150p' comment-section.tsxRepository: code-zero-to-one/study-platform-client
Length of output: 2664
🏁 Script executed:
cd src/features/study/group/channel/ui && cat -n sub-comments.tsx | head -80Repository: code-zero-to-one/study-platform-client
Length of output: 2522
🏁 Script executed:
cd src/features/study/group/channel/model && grep -A 10 "useThreadsQuery\|useCommentsQuery\|getComments" use-channel-query.ts | head -60Repository: code-zero-to-one/study-platform-client
Length of output: 1347
🏁 Script executed:
cd src/features/study/group/channel/ui && grep -r "Skeleton\|Loading\|Spinner" . --include="*.tsx" | head -20Repository: code-zero-to-one/study-platform-client
Length of output: 498
전체 섹션을 숨기지 말고 스레드 로딩만 확인하세요.
현재 코드는 allCommentsLoaded가 false인 동안 (즉, 모든 답글 로딩이 완료될 때까지) 전체 댓글 섹션을 null로 반환합니다. 그러나:
useThreadsQuery는placeholderData: keepPreviousData를 사용하여 이전 데이터를 유지합니다.- 각
SubComments컴포넌트는 이미 개별적으로 로딩 상태를 처리하고 있습니다 (if (isLoading) return null;). - 페이지 전환 시 전체 섹션이 공백이 되어 깜빡임이 발생합니다.
!allCommentsLoaded 조건을 제거하고 스레드 로딩만 확인하면, 답글이 개별적으로 로딩되는 동안에도 스레드는 표시되어 UX가 개선됩니다.
✅ 제안 수정안
- if (isLoading || !allCommentsLoaded) return null;
+ if (isLoading) return null;🤖 Prompt for AI Agents
In `@src/features/study/group/channel/ui/comment-section.tsx` around lines 68 -
70, The component currently returns null when either isLoading or
!allCommentsLoaded is true, hiding the entire comment section until every
subcomment finishes; change the logic to only gate on the thread-level loading
flag so subthreads can render while their replies load: remove the
!allCommentsLoaded check from the early return (keep only isLoading) in the
section that references isLoading and allCommentsLoaded (the block using
useThreadsQuery and rendering SubComments), relying on each SubComments
component to handle its own loading state.
| refetch: commentsRefetch, | ||
| } = useCommentsQuery(groupStudyId, threadId); | ||
|
|
||
| console.log(data, 'subcomment'); |
There was a problem hiding this comment.
디버그 로그는 제거하는 편이 좋습니다.
콘솔 노이즈 및 개인정보 노출 위험을 줄이기 위해 정리해주세요.
🧹 제안 수정안
- console.log(data, 'subcomment');📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| console.log(data, 'subcomment'); |
🤖 Prompt for AI Agents
In `@src/features/study/group/channel/ui/sub-comments.tsx` at line 32, Remove the
stray debug console.log in src/features/study/group/channel/ui/sub-comments.tsx
— specifically the console.log(data, 'subcomment') call; either delete it or
replace it with a proper, environment-guarded logger (e.g., only log in
development) or use existing app logging utilities so that sensitive data isn't
printed to the console.
url 기반으로 판단하는게 확장성있는 판단일까
…urney-path fix : sprint3 스터디 퍼널 개선
🌱 연관된 이슈
☘️ 작업 내용
code rabbit test 3
🍀 참고사항
스크린샷 (선택)
Summary by CodeRabbit
릴리스 노트
새로운 기능
버그 수정
스타일